iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Software Development

Re:從零開始做直播代購電商平台系列 第 16 篇

Day 16|優惠與金額(上):折扣算錯,比超賣還難查

  • 分享至 

  • xImage
  •  

超賣會炸、金流會叫,折扣算錯不會有任何聲音——客人多付了 13 元不會知道,少收了 13 元你也不會知道,直到對帳那天一筆一筆挖。這章講優惠:規則怎麼收納、聰明演算法怎麼輸給主播的一句話、以及讓分攤永遠加總相等的笨算術。

優惠的地圖:規則、慣例、實驗

當年的優惠分三個棲息地:

參數化你會重複的(券的三軸),隔離你不確定的(admin 裡的花式)。

  • 需要券的,收斂成三個正交參數:效果(折抵/免運)× 門檻(滿額多少,外加「哪些商品算進額度」的白名單)× 範疇(特定檔期/多個檔期/全檔期合算)。新券=填一組參數,不是寫一段 if——檔期第四次成為系統的自然邊界:券的生效範圍,直接用檔期表達。
  • 不需要券的多件優惠(買多少送多少),掛在商品上,一個商品可以掛多種——它的套用順序是下一節的主角。
  • 分類不了的,進 admin 試驗場:各種買 A 送 B 的花式組合,實驗性緊急套用——後台章(Day 14)的成熟度光譜,在優惠這裡有了最頻繁的用例:行銷的點子永遠比規則表快,試驗場讓點子先跑,站穩了才值得一個正式參數。

最優解輸給了順序

多件優惠可以疊,一個商品掛多種——那客人的購物車該套哪個組合?當年的第一版答案很工程師:寫一個最優惠組合演算法,幫客人算出全場最省的套法。這是組合優化,朝著 NP-hard 的方向長,但商品數不大,算是算得動。

然後主播說:不要。按照順序,每次採最大扣除就好。

我很久之後才真正理解這個要求有多對。最優解的問題不在算力,在它的答案沒辦法對人解釋:

  • 不穩定:客人多加一件商品,整個最優組合可能重排——螢幕上的折扣數字跳來跳去,客人不會覺得你聰明,只會覺得被坑。
  • 不能口播:主播在鏡頭前要能一句話講清楚規則。「按順序、每次挑折最多的」講得出口;「我們的演算法會為您求解全域最優」講出口就是客訴。
  • 不單調:最優解下,多買有時反而讓某個舊折扣消失——「我多買一件,那邊怎麼變貴了」是客服解釋不完的災難。

主播要的規則精確地說是:優惠有固定順序,依序一個套到滿、再換下一個——先把買 5 送 3 套到不能再套,才輪到買 3 送 2。答案穩定、單調、可預期——它不是全域最省,但每一步都看得懂。這跟狀態機被主播拔掉是同一族的故事:工程師寫了聰明的,現場要求換笨的,而現場是對的。收一句:演算法的正確性標準由使用情境定義——直播的「正確」,是客人聽得懂、數字不跳。

錢往哪放:三層各有欄位

優惠算完的結果,直接在 orders payment、order、order item 上開欄位——哪層合適放哪層。這不是隨性,是三層結構的正確用法:錢的欄位跟著它的語意住——商品自己的多件優惠寫在 item、檔期限定券寫在 order(它的範疇就是檔期)、全檔期合算的寫在 payment。金額一經結帳就定格(承諾點原則),發票、退款、對帳全部站在定格值上。

整數、捨去、減法:分攤的算術

金額全部用整數算——這是錢的程式碼的第一戒律。真正的考驗在分攤:一張檔期券折了 100 元,攤在兩件商品上;客人退其中一件,該退多少?

floor-and-subtract:捨去讓零頭有主(公司吸收),減法讓總和恆等——醜,但每一分錢都對得起來。

規則只有兩條:按比例算出的優惠後價格,無條件捨去小數(零頭讓給客人,公司吸收——爭議永遠往對客人有利的方向倒);最後一份不算比例,用減法補到總額(總和恆等是用結構保證的,不是用測試保證的)。這是 largest-remainder 分攤法的務實簡化,而且當年立了個好規矩:類似的情境一律照此處理——分攤算法全系統只有一種,對帳的人只需要理解一次。

重來會怎麼做

這章的當年設計大半保留:三軸參數表、admin 實驗層、固定順序套滿、floor-and-subtract 全是對的。重來的清單不長:

  1. 價格不是函數,是現場的決定。 直覺的修法是釘死一條價格解析優先序(特殊價 > 來源價 > style 價),把 m^n 的搜尋空間用規則消滅——但這是錯的:價格的優先序是主播的自由心證,系統管不了,也不該管。直播定價本來就是現場的藝術:貨是她談的、價是她喊的、要不要給這個客人特殊價是她的生意判斷。重來的正解不是消滅這個自由,是給它一個乾淨的落點——特殊價欄位就是「現場決定」的容器,系統的職責是把決定記成事實(誰、何時、定了什麼價),不是替現場推導價格。搜尋空間的變數,不是被規則消滅的,是被「定價那一刻」消滅的——人做出決定,變數就坍縮成常數。機制歸系統、政策歸人,又一次。
  2. 套用順序給明確的把手。 優惠掛明確的順序欄位,主播/營運指定;演算法簡化到極致——依序、逐一套滿,連「每步挑最大」都不需要,順序本身就是全部的規則:可口播、可預測、要改就改順序,不用改版。
  3. 分攤規則從慣例升級為一個 class。 floor-and-subtract 當年是「類似情況一律照此」的慣例;重來收斂成單一的 allocation policy class(Clean Architecture 的語意:它是一條領域規則,值得一個名字和一個家),配一條 property test——任何輸入,分攤加總恆等於整體。慣例會隨人員流動而漂,class 加測試不會。
  4. 發券前先 dry-run。 優惠引擎是純函式的形狀(購物車+規則 → 結果),天生可離線重放:新券上線前拿歷史訂單跑一輪,先看「這張券大概會花多少錢」——庫存章那次 migration 事故的教訓,同一招預防:動錢的規則,上線前先看數字。
  5. 花式買 A 送 B 不正式化。 規則表達力越強,規則引擎越接近一門程式語言;維持「參數表收斂已證明的、admin 隔離實驗的」雙層,就夠了。

反思

聰明演算法的墳墓,是解釋成本

最優組合演算法是我們寫過技術含量最高的程式碼之一,也是被砍得最快的之一。它輸給 greedy 的原因,值得每個工程師背下來:演算法的總成本=計算成本+解釋成本,而面向消費者的系統裡,解釋成本幾乎總是大頭。客人問「為什麼是這個折扣」時,客服要能答、主播要能講、工程師要能查——最優解在這三關全滅。這是主播第三次教我們設計(狀態機、重喊、greedy),而三次的教訓是同一條:現場智慧的核心是可解釋性,系統要嘛遷就它,要嘛被繞過。

參數化你會重複的,隔離你不確定的

優惠系統最怕長成 if 海——每檔活動加一段特判,兩年後沒人敢動。當年的結構避開了它:會重複的模式(滿額×範疇×效果)收斂成參數表,新券=填表;不確定的點子(花式買 A 送 B)隔離進 admin 試驗場,爛了就丟、站穩了才升格。**規則引擎的真義不是「能表達一切」——是把重複的變便宜、把實驗的變安全。**兩層各司其職,行銷的創意速度和系統的可維護性才能同時活著。

錢的正確性是會計性質,不是數學性質

floor-and-subtract 在數學家眼裡很醜:分攤不按精確比例、最後一份用減法硬補。但錢的程式碼要回答的從來不是「分得準不準」,是**「加總相不相等、零頭歸不歸屬、事後查不查得清」**——這是會計的標準,不是數學的標準。0.1 元分給誰根本不重要,重要的是每一分錢都有主、退款永遠退得出一個明確的數、對帳永遠對得平。錢的程式碼,優雅讓位給對帳——這句話值得貼在每個電商工程師的螢幕上。

那個被砍掉的最優解演算法,到底是不是 NP-hard?明天用一整篇做誠實的複雜度鑑定。


本文改寫自我的部落格系列《Re:從零開始做直播代購電商平台》,本篇完整版:https://blog.aidan.tw/blog/rezero-promotion/


上一篇
Day 15|權限:誰能按哪顆按鈕——我們改了三次
下一篇
Day 17|優惠與金額(下):最優惠組合是 NP-hard 嗎?
系列文
Re:從零開始做直播代購電商平台 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言